﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Threading;
using System.Security;
using SMTP.Test.Client.Requests;
using Heijden.DNS;

namespace SMTP.Test.Client
{
    //public class RequestResults : EventArgs
    //{
    //    public string Results { get; private set; }

    //    public RequestResults(string results)
    //    {
    //        Results = results;
    //    }
    //}

    class vmTestClient : INotifyPropertyChanged
    {
        //enum for current task running
        //used to decide which task to cancel
        private enum CurrentRequest { None, Ping, Email, PortScan, DNSQuery }
        private CurrentRequest _currentRequest;

        //events
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler RequestComplete;
        //public event EventHandler<RequestResults> RequestComplete;
        
        //default stmp server port
        const int DEFAULT_SMTP_PORT = 25;

        //email settings
        int _port = DEFAULT_SMTP_PORT;
        string _server = string.Empty;
        string _to = string.Empty;
        string _from = string.Empty;
        string _subject = string.Empty;
        string _body = string.Empty;
        string _attachment = string.Empty;

        //email secure settings
        bool _sendSSL = false;
        bool _sendCredentials = false;
        string _username = string.Empty;
        string _password;

        //controls wrapping of log textbox
        bool _wraptext = false;
        TextWrapping _wraptype = TextWrapping.NoWrap;

        //logging 
        StringBuilder _sessionlog = new StringBuilder();

        //request objects
        MailTest _mail;
        PortScanTest _portScan;
        PingTest _ping;
        DNSRecordTest _dnslookup;

        //used to close drop down list once ping selection is made
        private bool _pingDropDownIsOpen = false;

        //cancellation related properties
        List<CurrentRequest> _requestPermittedCancellation = new List<CurrentRequest>();
        public bool CancellationInitiated { get; private set; }
        public bool CancellationPermitted { get; private set; }

        public vmTestClient()
        {
            //set default state of task
            _currentRequest = CurrentRequest.None;

            //add requests allowed to cancel
            _requestPermittedCancellation.Add(CurrentRequest.Ping);
            _requestPermittedCancellation.Add(CurrentRequest.Email);

            // create the new escalation policy
            TaskScheduler.UnobservedTaskException +=
            (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
            {
                // mark the exception as being handled
                eventArgs.SetObserved();
                // get the aggregate exception and process the contents
                ((AggregateException)eventArgs.Exception).Handle(ex =>
                {
                    // write the type of the exception to the console
                    Console.WriteLine("Exception type: {0}", ex.GetType());
                    return true;
                });
            };
        }

        //used to close drop down list once ping selection is made
        public bool PingDropDownIsOpen
        {
            get { return _pingDropDownIsOpen; }
            set
            {
                if (value != _pingDropDownIsOpen)
                {
                    _pingDropDownIsOpen = value;
                    OnPropertyChanged("PingDropDownIsOpen");
                }
            }
        }

        //used to close drop down list once dns selection is made
        private bool _dnsDropDownIsOpen = false;
        public bool DNSDropDownIsOpen
        {
            get { return _dnsDropDownIsOpen; }
            set
            {
                if (value != _dnsDropDownIsOpen)
                {
                    _dnsDropDownIsOpen = value;
                    OnPropertyChanged("DNSDropDownIsOpen");
                }
            }
        }

        //used by logging text box to indicate whether or not user can add notes
        //user cannot add notes while we're running a request
        private bool _runningRequest = false;
        public bool RunningRequest
        {
            get { return _runningRequest; }
            set
            {
                if (value != _runningRequest)
                {
                    _runningRequest = value;
                    OnPropertyChanged("RunningRequest");
                }
            }
        }

        private void IniRequest(CurrentRequest request)
        {
            //set current task for cancellation
            _currentRequest = request;

            //set cancellation initiated to false
            CancellationInitiated = false;

            //close drop down button
            PingDropDownIsOpen = false;

            //close dns combo box drop down list
            DNSDropDownIsOpen = false;

            //determine if request is permitted to cancel
            if (_requestPermittedCancellation.Contains(request))
            {
                CancellationPermitted = true;
            }
            else
            {
                CancellationPermitted = false;
            }

            //inform UI that we are running a request
            RunningRequest = true;
        }

        #region Requests (Email, Ping, etc...) & Cancellation
        public void SendMail()
        {
            //initialize request for GUI
            IniRequest(CurrentRequest.Email);

            //start sending of email
            AddSessionLogEntry("Begin sending email");
            AddSessionLogEntry("Server: " + Server);
            AddSessionLogEntry("Port: " + Port);
            AddSessionLogEntry("To: " + To);
            AddSessionLogEntry("From: " + From);
            AddSessionLogEntry("Subject: " + Subject);
            AddSessionLogEntry("Body: " + Body);

            if (Attachment.Length > 0)
            {
                AddSessionLogEntry("Attachment: " + Attachment);
            }
            else
            {
                AddSessionLogEntry("Attachment: None");
            }

            if (SendCredentials)
                AddSessionLogEntry("Sending user credentials");

            if (SendSSL)
                AddSessionLogEntry("Sending encrypted SSL");

            if (SendCredentials)
            {
                _mail = new MailTest(Server, From, To, Subject, Body, Attachment, Username, Password, SendCredentials, SendSSL, Convert.ToInt32(Port));
            }
            else
            {
                _mail = new MailTest(Server, From, To, Subject, Body, Attachment, Convert.ToInt32(Port));
            }

            //setup our callback from mail
            _mail.MailSent += (s, e) =>
            {
                //reset current task
                _currentRequest = CurrentRequest.None;

                //indicate to interface we're done running request
                RunningRequest = false;
                AddSessionLogEntry(e.Results);
                OnRequestComplete();
            };

            _mail.SendMail();
        }

        public void PingHost()
        {
            //initialize gui for request
            IniRequest(CurrentRequest.Ping);

            _ping = new PingTest(Server);

            _ping.PingResponseMessage += (s, e) =>
            {
                AddSessionLogEntry(e.Response);
            };

            _ping.PingComplete += (s, e) =>
            {
                //reset current task to none
                _currentRequest = CurrentRequest.None;

                //indicate to user interface that we're done
                RunningRequest = false;

                //essentially same as above
                OnRequestComplete();
            };

            _ping.PingHost();
        }

        public void PingHostForever()
        {
            //initialize gui for request
            IniRequest(CurrentRequest.Ping);

            _ping = new PingTest(Server, -1);

            _ping.PingResponseMessage += (s, e) =>
            {
                AddSessionLogEntry(e.Response);
            };

            _ping.PingComplete += (s, e) =>
            {
                //reset current task to none
                _currentRequest = CurrentRequest.None;

                //indicate to user interface that we're done
                RunningRequest = false;

                //essentially same as above
                OnRequestComplete();
            };

            _ping.PingHost();
        }

        //CancellationTokenSource sourceToken;
        public void ScanPort()
        {
            //initialize gui for request
            IniRequest(CurrentRequest.PortScan);
            
            _portScan = new PortScanTest(Server, Convert.ToInt32(Port), 2000);

            _portScan.ScanComplete += (s, e) =>
            {
                RunningRequest = false;
                AddSessionLogEntry(e.Results);
                OnRequestComplete();
            };
            _portScan.ScanPort();
        }

        public void DNSLookup(QType qtype, QClass qclass)
        {
            //initialize gui for request
            IniRequest(CurrentRequest.DNSQuery);

            _dnslookup = new DNSRecordTest(Server, qtype, qclass);

            _dnslookup.DNSQueryComplete += (s, e) =>
            {
                RunningRequest = false;
                OnRequestComplete();
            };

            _dnslookup.DNSResponseMessage += (s, e) =>
            {
                AddSessionLogEntry(e.Response);
            };

            _dnslookup.QueryDNS();
        }

        public void CancelTask()
        {
            //cause the cancel button to become inoperable
            CancellationInitiated = true;

            //add quick log entry
            AddSessionLogEntry("Cancellation initiated");

            //use a switch statement...really need to refactor using an interface for requests
            switch (_currentRequest)
            {
                case CurrentRequest.Ping:
                    _ping.Cancel();
                    break;
                case CurrentRequest.Email:
                    _mail.Cancel();
                    break;
                case CurrentRequest.PortScan:
                    break;
                default:
                    //nothing to do
                    break;
            }
        }
        #endregion

        #region Logging Specific Methods & Settings
        public string SessionLog
        {
            get
            {
                return _sessionlog.ToString();
            }
        }

        public void AddSessionLogEntry(string message)
        {
            string msg = DateTime.Now.ToString() + " - " + message + "\r\n";
            _sessionlog.Append(msg);
            OnPropertyChanged("SessionLog");
        }

        public void SaveNotes(string notes)
        {
            _sessionlog.Clear();
            _sessionlog.Append(notes);
            //OnPropertyChanged("SessionLog");
        }

        public void ClearSessionLog()
        {
            _sessionlog.Clear();
            OnPropertyChanged("SessionLog");
        }

        public bool WrapText
        {
            get { return _wraptext; }
            set
            {
                if (value != _wraptext)
                {
                    _wraptext = value;
                    if (_wraptext)
                    {
                        WrapType = TextWrapping.Wrap;
                    }
                    else
                    {
                        WrapType = TextWrapping.NoWrap;
                    }

                    OnPropertyChanged("WrappingEnabled");
                }
            }
        }

        public TextWrapping WrapType
        {
            get { return _wraptype; }
            set
            {
                if (_wraptype != value)
                {
                    _wraptype = value;
                    OnPropertyChanged("WrapType");
                }
            }
        }
        #endregion

        #region Server & Port (used by all requests)
        public string Server
        {
            get
            {
                return GetValues(ref _server);
            }
            set
            {
                if (SetValues(ref _server, value))
                {
                    OnPropertyChanged("Server");
                }
            }
        }

        public string Port
        {
            get
            {
                return _port.ToString();
            }
            set
            {
                int tmpPort;

                if (Int32.TryParse(value, out tmpPort))
                {
                    if (tmpPort != _port)
                    {
                        _port = tmpPort;
                        OnPropertyChanged("Port");
                    }
                }
            }
        }
        #endregion

        #region Email Specific Settings
        public string To
        {
            get
            {
                return GetValues(ref _to);
            }
            set
            {
                if (SetValues(ref _to, value))
                {
                    OnPropertyChanged("To");
                }
            }
        }

        public string From
        {
            get
            {
                return GetValues(ref _from);
            }
            set
            {
                if (SetValues(ref _from, value))
                {
                    OnPropertyChanged("From");
                }
            }
        }

        public string Subject
        {
            get
            {
                return GetValues(ref _subject);
            }
            set
            {
                if (SetValues(ref _subject, value))
                {
                    OnPropertyChanged("Subject");
                }
            }
        }

        public string Body
        {
            get
            {
                return GetValues(ref _body);
            }
            set
            {
                if (SetValues(ref _body, value))
                {
                    OnPropertyChanged("Body");
                }
            }
        }

        public string Attachment
        {
            get 
            { 
                return GetValues(ref _attachment); 
            }
            set
            {
                if (SetValues(ref _attachment, value))
                {
                    OnPropertyChanged("Attachment");
                }
            }
        }

        public bool SendCredentials
        {
            get { return _sendCredentials; }
            set
            {
                if (value != _sendCredentials)
                {
                    _sendCredentials = value;
                    OnPropertyChanged("SendCredentials");
                }
            }
        }

        public bool SendSSL
        {
            get { return _sendSSL; }
            set
            {
                if (value != _sendSSL)
                {
                    _sendSSL = value;
                    OnPropertyChanged("SendSSL");
                }
            }
        }

        public string Username
        {
            get { return GetValues(ref _username); }
            set
            {
                if (SetValues(ref _username, value))
                {
                    OnPropertyChanged("Username");
                }
            }
        }

        public string Password
        {
            get { return _password; }
            set
            {
                if (value != _password)
                {
                    _password = value;
                }
            }
        }
        #endregion

        #region Setting & Getting String Values (used to ensure we don't return NULL to the page)
        //used to ensure we aren't returning null values
        private string GetValues(ref string param)
        {
            if (param == null) param = string.Empty;

            return param;
        }

        private bool SetValues(ref string param, string value)
        {
            bool blnPropertyChanged = false;
            string strNewValue = string.Empty;

            if (value != null) strNewValue = value.Trim();

            if (strNewValue != param)
            {
                param = strNewValue;
                blnPropertyChanged = true;
            }

            return blnPropertyChanged;
        }
        #endregion

        #region Event Handling
        private void OnPropertyChanged(string property)
        {
            var hanlder = PropertyChanged;

            if (hanlder != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
        }

        private void OnRequestComplete()
        {
            var handler = RequestComplete;
            if (handler != null) RequestComplete(this, EventArgs.Empty);
        }
        #endregion
    }
}
